package no.ntnu.item.ttm4120.dhcp;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.TreeMap;

import no.ntnu.item.ttm4120.dhcp.data.AddressPoolEntity;
import no.ntnu.item.ttm4120.dhcp.exception.AddressPoolException;

public class AddressPool {

	private TreeMap<String, AddressPoolEntity> pool = new TreeMap<String, AddressPoolEntity>();
	//TODO: read from conf file
	private static long defaultLease = 20000;
	private String startIP = "192.168.1.1";
	private String endIP = "192.168.1.254";
	private InetAddress lastAssignedIP = null;

	/** 
	 * Change the value during crash of the other server,
	 * so this pool can provid corresponding server ips from failed pool
	 */
	private boolean takeOver = false;
	private int serverPosition = -1;
	
	public AddressPool(int serverPosition) {
		this.serverPosition = serverPosition;
	}
	/**
	 * Same junk here, initial address should not be here, instead should be handle in getNextIP
	 */
	private void init(){
	}
	
	/**
	 * Use this method with caution, 
	 * only corresponding server can call this,
	 * while it get an update msg from its replica
	 */
	public void updatePool(AddressPoolEntity addressPoolEntity, String issuerServer){
		System.out.println("Upldate local AddressPool due to change message received from: " + issuerServer + ", Address: " + addressPoolEntity);
		getPool().put(addressPoolEntity.getAddress().getHostAddress(), addressPoolEntity);
	}
	
	public AddressPoolEntity getNewAddress(String client, long lease) throws AddressPoolException{
		AddressPoolEntity address = null;
		
		InetAddress newIP = getNextIP(shouldTakeOver());
		address = new AddressPoolEntity();
		address.setAddress(newIP);
		address.setAssigned(true);
		address.setClient(client);
		address.setLeaseTimeEnd(lease + System.currentTimeMillis());
//		System.out.println("Current Time: 		  " + System.currentTimeMillis());
//		System.out.println("Expected expire Time: " + address.getLeaseTimeEnd());
		address.setServer(serverPosition);
		
		getPool().put(newIP.getHostAddress(), address);
		System.out.println("New Address has been given: " + address);
		System.out.println();
		return address;
	}
	
	/**
	 * Real junk, should be rewrite more wisely.
	 * IPv4 only, /24
	 * @return
	 */
	private InetAddress getNextIP(boolean takeOver) throws AddressPoolException{
		InetAddress lastIP;
		InetAddress ip2 = null;
		boolean alreadyAssigned;
		int loop = 0;
		
		try {
			// Handle first IP in the address pool or part of address pool corresponding to this server
			if(getPool().size() == 0 || lastAssignedIP == null){
				if(serverPosition == 0){
					ip2 = InetAddress.getByAddress(new byte[]{(byte)192, (byte)168, (byte)1, (byte)1});
				}else{
					ip2 = InetAddress.getByAddress(new byte[]{(byte)192, (byte)168, (byte)1, (byte)128});
				}
				lastAssignedIP = ip2;
				return ip2;
			}
			
			System.out.println( getPool().keySet());
//			AddressPoolEntity add = (AddressPoolEntity) getPool().values().toArray()[getPool().size() - 1];
			lastIP = lastAssignedIP;
			System.out.println("LastAssignedIP: " + lastIP);
			alreadyAssigned = true;
			
			while(alreadyAssigned){
				loop++;
				if (loop > 254) throw new AddressPoolException("Loop Experienced, no more addresses.");
//						System.out.println(lastIP.getAddress()[3]);
				if(lastIP.getAddress()[3] == -2){
					System.out.println("End of the pool, returning to the top" );
					System.out.println(lastIP.getAddress()[3]);
					System.out.println(lastIP);
					ip2 = InetAddress.getByAddress(new byte[]{
							lastIP.getAddress()[0],
							lastIP.getAddress()[1],
							lastIP.getAddress()[2],
							(byte)(1)});
				}else{
//							System.out.println("1:"+lastIP.getAddress()[3]);
					ip2 = InetAddress.getByAddress(new byte[]{
							lastIP.getAddress()[0],
							lastIP.getAddress()[1],
							lastIP.getAddress()[2],
							(byte)(lastIP.getAddress()[3] + 1)});
//							System.out.println("2:"+ip2.getAddress()[3]);
					System.out.println(ip2);
				}
				if (getPool().get(ip2.getHostAddress()) == null ||
						!getPool().get(ip2.getHostAddress()).isAssigned()){
					alreadyAssigned = false;
//							break;
				}else{
					lastIP = ip2;
					System.out.println(getPool().get(ip2.getHostAddress()));
					System.out.println(ip2.getHostAddress());
					System.out.println("Trying next IP");
				}
			}
			
			if(checkRange(ip2, serverPosition, takeOver)){
				System.out.println("NewIP: " + ip2);
				lastAssignedIP = ip2;
				return ip2;
			}else{
				throw new AddressPoolException();
			}

		} catch (UnknownHostException e) {
			e.printStackTrace();
			throw new AddressPoolException(e);
		}
	}
	
	private boolean checkRange(InetAddress ip2, int serverPos, boolean takeOver) {
		// TODO: use input for start and end of the range. 
		int start = 1;
		int end = 254;
		// TODO: check not only the last part. 
		int lastPart = ip2.getAddress()[3];
		if(lastPart < 0) lastPart = (lastPart + 256);
			
		if(lastPart <= (serverPos*((end - start + 1)/2) + 127) &&
				lastPart >= (serverPos*((end - start + 1)/2) + 1)){
			System.out.println("Given address was in rage.");
			return true;
		}else if(takeOver && lastPart <= end && lastPart >= start){
			System.out.println("TakeOver case.");
			return true;
		}else{
			return false;
		}
	}
	
	public static void main(String[] args) {
		AddressPool pool = new AddressPool(0);
		pool.setTakeOver(true);
		for (int i = 0; i < 255; i++) {
			try {
				pool.getNewAddress("Client" + i, 20000);
				System.out.println("------------------------------------------");
			} catch (AddressPoolException e) {
				e.printStackTrace();
			}
		}
		System.err.println();
		System.err.println("Pool size:" + pool.getPool().size());
		System.err.println();
	}
	
	public static long getDefaultLease() {
		return defaultLease;
	}
	public static  void setDefaultLease(long defaultl) {
		defaultLease = defaultl;
	}

	public boolean shouldTakeOver() {
		return takeOver;
	}
	public void setTakeOver(boolean takeOver){
		this.takeOver = takeOver;
	}
	public void setServerPosition(int serverPosition) {
		this.serverPosition = serverPosition;
	}
	public int getServerPosition() {
		return serverPosition;
	}

	public String getStartIP() {
		return startIP;
	}
	public void setStartIP(String startIP) {
		this.startIP = startIP;
	}
	
	public String getEndIP() {
		return endIP;
	}
	public void setEndIP(String endIP) {
		this.endIP = endIP;
	}
	/**
	 * Warning: This should only be done after recovering from crash. 
	 * @param pool
	 */
	public void setPool(TreeMap<String, AddressPoolEntity> pool) {
		System.out.println("Syncing AddressPool");
		int maxLastPart = -1, lastPart = -1;
		InetAddress lastIP = null;
		int start = 1;
		int end = 254;
		int serverPos = serverPosition;
		this.pool = pool;
		for (AddressPoolEntity address: getPool().values()) {
			lastPart = address.getAddress().getAddress()[3];
			if(lastPart < 0) lastPart = (lastPart + 256);
			
			if(lastPart <= (serverPos*((end - start + 1)/2) + 127) &&
					lastPart >= (serverPos*((end - start + 1)/2) + 1)){
				System.out.println("Given address was in rage. Checking for largest");
				if (maxLastPart < lastPart){
					maxLastPart = lastPart;
					lastIP = address.getAddress();
				}
			}else if(lastPart <= end && lastPart >= start){
				System.out.println("No Address in the corresponding server part.");
				lastAssignedIP = null;
			}else{
				System.out.println("Somethings bad happened?????????????????");
			}
		}
		lastAssignedIP = lastIP;
	}
	public TreeMap<String, AddressPoolEntity> getPool() {
		return pool;
	}
}
